package com.illtamer.infinite.collapsar.dispatcher.builder;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Consumer;

/**
 * MessageBuilder for MIME-SMTP
 * <br>
 * Example1 (useSSL=false):
 * <pre>
 *     Message smtp = MessageBuilder.mime()
 *     .properties()
 *         .add(MailFields.HOST, "mx1.qq.com")
 *         .add(MailFields.TRANSPORT_PROTOCOL, "smtp")
 *         .add(MailFields.SMTP.PORT, "25")
 *     .unauthenticated()
 *         .debug()
 *     .message()
 *         .source("Infinite_Server", "<IllTamer@mail.zip1mask.top>")
 *         .to("765743073@qq.com")
 *         .consume(message -> {
 *           message.setSubject("Hello IllTamer");
 *           message.setText("awa\r\n2333333333333333\n666");
 *         })
 *     .done();
 * </pre>
 * Example2 (useSSL=true)
 * <pre>
 *     MailSSLSocketFactory sf = new MailSSLSocketFactory();
 *     sf.setTrustAllHosts(true);
 *     Message smtp = MessageBuilder.mime()
 *             .properties()
 *                 .add(MailFields.HOST, "smtp.qq.com")
 *                 .add(MailFields.TRANSPORT_PROTOCOL, "smtp")
 *                 .add(MailFields.SMTP.AUTH, "true")
 *                 .add(MailFields.SMTP.PORT, "465")
 *                 .add(MailFields.SMTP.SSL.ENABLE, "true")
 *                 .add(MailFields.SMTP.SSL.SOCKET_FACTORY, sf)
 *             .authenticate(new Authenticator() {
 *                 protected PasswordAuthentication getPasswordAuthentication() {
 *                     return new PasswordAuthentication("765743073@qq.com", "");
 *                 }
 *             })
 *                 .debug()
 *             .message()
 *                 .source("<765743073@qq.com>")
 *                 .to("765743073@qq.com")
 *                 .consume(message -> {
 *                     message.setSubject("Hello IllTamer");
 *                     message.setText("awa\r\n2333333333333333");
 *                 })
 *             .done();
 * </pre>
 * */
public class SMTPMessageBuilder {

    private final MessageBuilder messageBuilder;

    protected SMTPMessageBuilder(MessageBuilder messageBuilder) {
        this.messageBuilder = messageBuilder;
    }

    private PropertiesBuilder propertiesBuilder;

    private SessionBuilder sessionBuilder;

    /**
     * Return a {@link PropertiesBuilder}. It will create
     * new one if not exists.
     * */
    public PropertiesBuilder properties() {
        return Optional.ofNullable(propertiesBuilder).orElseGet(
                () -> properties(null)
        );
    }

    /**
     * Return a {@link PropertiesBuilder} with existing {@link Properties}.
     * It will create new one if not exists.
     * */
    public PropertiesBuilder properties(Properties properties) {
        return Optional.ofNullable(propertiesBuilder).orElseGet(
                () -> propertiesBuilder = new PropertiesBuilder(properties == null ? new Properties() : properties)
        );
    }

    public class PropertiesBuilder {

        private final Properties properties;

        private PropertiesBuilder(Properties properties) {
            this.properties = properties;
        }

        public PropertiesBuilder add(Object key, Object value) {
            properties.put(key, value);
            return this;
        }

        public PropertiesBuilder custom(Consumer<Properties> consumer) {
            consumer.accept(properties);
            return this;
        }

        /**
         * Return a {@link PropertiesBuilder}. It will create
         * new one if not exists.
         * */
        public SessionBuilder unauthenticated() {
            return Optional.ofNullable(sessionBuilder).orElseGet(
                    () -> authenticate(null, null)
            );
        }

        /**
         * Return a {@link PropertiesBuilder} with existing {@link Properties}.
         * It will create new one if not exists.
         * */
        public SessionBuilder unauthenticated(Session session) {
            return Optional.ofNullable(sessionBuilder).orElseGet(
                    () -> authenticate(null, session)
            );
        }

        /**
         * Return a {@link PropertiesBuilder}. It will create
         * new one if not exists.
         * */
        public SessionBuilder authenticate(Authenticator authenticator) {
            return Optional.ofNullable(sessionBuilder).orElseGet(
                    () -> authenticate(authenticator, null)
            );
        }

        /**
         * Return a {@link PropertiesBuilder} with existing {@link Properties}.
         * It will create new one if not exists.
         * */
        public SessionBuilder authenticate(Authenticator authenticator, Session session) {
            return Optional.ofNullable(sessionBuilder).orElseGet(
                    () -> sessionBuilder = new SessionBuilder(session == null ? Session.getInstance(properties, authenticator) : session)
            );
        }
    }

    public class SessionBuilder {

        private final Session session;

        private SessionBuilder(Session session) {
            this.session = session;
        }

        public SessionBuilder debug() {
            session.setDebug(true);
            return this;
        }

        public SessionBuilder custom(Consumer<Session> consumer) {
            consumer.accept(session);
            return this;
        }

        /**
         * Return a new {@link Message}
         * */
        public MessageBuilder message() {
            messageBuilder.message = new MimeMessage(session);
            return messageBuilder;
        }
    }

    public Properties getProperties() {
        return propertiesBuilder.properties;
    }

    public Session getSession() {
        return sessionBuilder.session;
    }

}
